package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.list.repository.GoodsRepository;
import com.atguigu.gmall.list.service.SearchService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseAttrInfo;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import org.apache.lucene.search.join.ScoreMode;
import org.checkerframework.checker.units.qual.A;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

/**
 * @author atguigu-mqx
 */
@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private ProductFeignClient productFeignClient;
    //  需要引入es的客户端!
    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Autowired
    private RedisTemplate redisTemplate;

    //  操作es 的高级客户端
    @Autowired
    private RestHighLevelClient restHighLevelClient;


    //  上架
    @Override
    public void upperGoods(Long skuId) {
        //  创建对象
        Goods goods = new Goods();
        //        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
        //        BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
        //        BaseTrademark trademark = productFeignClient.getTrademark(skuInfo.getTmId());
        //        List<BaseAttrInfo> attrList = productFeignClient.getAttrList(skuId);
        //  异步编排：
        CompletableFuture<SkuInfo> skuInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
            goods.setTitle(skuInfo.getSkuName());
            goods.setCreateTime(new Date());
            goods.setDefaultImg(skuInfo.getSkuDefaultImg());
            //  goods.setPrice(skuInfo.getPrice().doubleValue()); // 如果你怕这个数据是过时的，可以远程调用！
            goods.setPrice(productFeignClient.getSkuPrice(skuId).doubleValue());
            goods.setId(skuId);
            return skuInfo;
        },threadPoolExecutor);

        //  获取分类数据
        CompletableFuture<Void> completableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {
            BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
            goods.setCategory1Id(categoryView.getCategory1Id());
            goods.setCategory2Id(categoryView.getCategory2Id());
            goods.setCategory3Id(categoryView.getCategory3Id());
            goods.setCategory1Name(categoryView.getCategory1Name());
            goods.setCategory2Name(categoryView.getCategory2Name());
            goods.setCategory3Name(categoryView.getCategory3Name());

        }, threadPoolExecutor);

        //  获取品牌数据
        CompletableFuture<Void> trademarkCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {
            BaseTrademark trademark = productFeignClient.getTrademark(skuInfo.getTmId());
            goods.setTmId(trademark.getId());
            goods.setTmName(trademark.getTmName());
            goods.setTmLogoUrl(trademark.getLogoUrl());
        }, threadPoolExecutor);

        //  获取平台属性数据
        CompletableFuture<Void> attrListCompletableFuture = CompletableFuture.runAsync(() -> {
            List<BaseAttrInfo> attrList = productFeignClient.getAttrList(skuId);
            //  循环遍历
            List<SearchAttr> attrsList = attrList.stream().map((baseAttrInfo -> {
                //  给 SearchAttr 赋值
                SearchAttr searchAttr = new SearchAttr();
                //  赋值
                searchAttr.setAttrId(baseAttrInfo.getId());
                searchAttr.setAttrName(baseAttrInfo.getAttrName());
                //  平台属性值
                searchAttr.setAttrValue(baseAttrInfo.getAttrValueList().get(0).getValueName());
                return searchAttr;
            })).collect(Collectors.toList());

            goods.setAttrs(attrsList);
        }, threadPoolExecutor);

        //  多任务组合：
        CompletableFuture.allOf(skuInfoCompletableFuture,
                completableFuture,
                trademarkCompletableFuture,
                attrListCompletableFuture
                ).join();
        //  赋值即可！
        this.goodsRepository.save(goods);
    }

    @Override
    public void lowerGoods(Long skuId) {
        //  下架
        this.goodsRepository.deleteById(skuId);
    }

    @Override
    public void updHotScore(Long skuId) {
        //  需要记录这个商品被访问的次数 或 获取这个商品被访问的次数！
        //  redis 的客户端，以及 key！ 数据类型！
        //  分析数据类型！
        //  String INCRBY key increment     key = hot:skuId:info
        //  Zset ZINCRBY key increment member  key = hotscore member = hot:skuId
        String key = "hotScore";
        //  参数 key , 第二个member！ 第三个步长
        Double count = this.redisTemplate.opsForZSet().incrementScore(key, "skuId:" + skuId, 1);
        //  判断
        if(count%10==0){
            //  更新es 将原来的hotScore 这个字段重新赋值！
            Optional<Goods> optional = this.goodsRepository.findById(skuId);
            Goods goods = optional.get();
            goods.setHotScore(count.longValue());
            this.goodsRepository.save(goods);
        }
    }

    @Override
    public SearchResponseVo search(SearchParam searchParam) throws IOException {
        //  检索方法：
        /*
        1.  生产dsl语句
        2.  执行dsl语句
        3.  获取到执行之后的结果集
         */
        //  生产dsl语句 需要使用哪个客户端来操作es！
        SearchRequest searchRequest = this.buildQueryDsl(searchParam);
        //  执行dsl语句
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        //  需要searchResponse ---> SearchResponseVo
        SearchResponseVo searchResponseVo = this.parseSearchResult(searchResponse);
        //  trademarkList  attrsList  goodsList total 在 parseSearchResponseVo 方法中赋值！
        // pageSize，pageNo，totalPages 在这个方法中赋值即可！

        searchResponseVo.setPageSize(searchParam.getPageSize());
        searchResponseVo.setPageNo(searchParam.getPageNo());
        //  3 条数据： 10 3 4 | 9 3 3
        //  long totalPages =  searchResponseVo.getTotal()%searchParam.getPageSize()==0?searchResponseVo.getTotal()/searchParam.getPageSize():searchResponseVo.getTotal()/searchParam.getPageSize()+1;
        //  计算总页数
        long totalPages = (searchResponseVo.getTotal()+searchParam.getPageSize()-1)/searchParam.getPageSize();

        searchResponseVo.setTotalPages(totalPages);

        //  返回数据
        return searchResponseVo;
    }

    //  设置返回结果集
    private SearchResponseVo parseSearchResult(SearchResponse searchResponse) {
        //  声明对象
        SearchResponseVo searchResponseVo = new SearchResponseVo();
        //  private List<SearchResponseTmVo> trademarkList;
        //  private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
        //  private List<Goods> goodsList = new ArrayList<>();
        //  private Long total;

        //  获取到hits 对象
        SearchHits hits = searchResponse.getHits();
        //  获取到subHits 对象
        SearchHit[] subHits = hits.getHits();
        //  循环遍历
        //  定义一个goods集合！
        List<Goods> goodsList = new ArrayList<>();
        if (subHits!=null && subHits.length>0){
            for (SearchHit subHit : subHits) {
                //  获取到source
                String sourceAsString = subHit.getSourceAsString();
                //  转换为goods
                Goods goods = JSON.parseObject(sourceAsString, Goods.class);
                //  判断是否有高亮显示
                if (subHit.getHighlightFields().get("title")!=null) {
                    //  有高亮就需要获取到高亮中的名称！
                    Text title = subHit.getHighlightFields().get("title").getFragments()[0];
                    //  设置高亮名称
                    goods.setTitle(title.toString());
                }
                //  将goods 添加到集合
                goodsList.add(goods);
            }
            //            List<SearchHit> searchHits = Arrays.asList(subHits);
            //            List<Goods> goodList = searchHits.stream().map((subHit) -> {
            //                //  获取到source
            //                String sourceAsString = subHit.getSourceAsString();
            //                //  转换为goods
            //                Goods goods = JSON.parseObject(sourceAsString, Goods.class);
            //                //  判断是否有高亮显示
            //                if (subHit.getHighlightFields().get("title") != null) {
            //                    //  有高亮就需要获取到高亮中的名称！
            //                    Text title = subHit.getHighlightFields().get("title").getFragments()[0];
            //                    //  设置高亮名称
            //                    goods.setTitle(title.toString());
            //                }
            //                return goods;
            //            }).collect(Collectors.toList());
            //            //  赋值操作！
            //            searchResponseVo.setGoodsList(goodList);
        }

        //  赋值：
        searchResponseVo.setGoodsList(goodsList);
        //  获取品牌集合数据    ----    从聚合中获取
        //  key = tmIdAgg | attrAgg     value = Aggregation
        Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();
        //  参考官网！
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggregationMap.get("tmIdAgg");
        //  通过获取桶中的数据，并赋值给SearchResponseTmVo 并返回！
        //  声明一个集合
        //        List<SearchResponseTmVo> trademarkList = new ArrayList<>();
        //        List<? extends Terms.Bucket> buckets = tmIdAgg.getBuckets();
        //        buckets.forEach((bucket)->{
        //            String tmId = bucket.getKeyAsString();
        //            SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();
        //
        //            searchResponseTmVo.setTmId(Long.parseLong(tmId));
        //            //.tmNameAgg  tmLogoUrlAgg
        //            trademarkList.add(searchResponseTmVo);
        //        });

        List<SearchResponseTmVo> trademarkList = tmIdAgg.getBuckets().stream().map((bucket) -> {
            String tmId = bucket.getKeyAsString();
            //  声明一个品牌对象
            SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();
            //  给品牌Id 赋值
            searchResponseTmVo.setTmId(Long.parseLong(tmId));
            //  赋值品牌名称
//            Map<String, Aggregation> stringAggregationMap = ((Terms.Bucket) bucket).getAggregations().asMap();
//            stringAggregationMap.get("tmNameAgg");
            ParsedStringTerms tmNameAgg = ((Terms.Bucket) bucket).getAggregations().get("tmNameAgg");
            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmName(tmName);

            //  赋值品牌logoUrl
            ParsedStringTerms tmLogoUrlAgg = ((Terms.Bucket) bucket).getAggregations().get("tmLogoUrlAgg");
            String tmLogoUrl = tmLogoUrlAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmLogoUrl(tmLogoUrl);
            //  返回品牌对象！
            return searchResponseTmVo;
        }).collect(Collectors.toList());

        //  赋值品牌集合数据
        searchResponseVo.setTrademarkList(trademarkList);

        //  获取平台属性集合数据      ----    从聚合中获取      ----- 特殊性：数据类型是nested！
        ParsedNested attrAgg = (ParsedNested) aggregationMap.get("attrAgg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        List<SearchResponseAttrVo> attrsList = attrIdAgg.getBuckets().stream().map((bucket) -> {
            // 获取平台 属性Id 平台属性名称，平台属性值名称！
            //  声明一个对象
            SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();
            //  赋值 属性Id 平台属性名称，平台属性值名称！
            Number keyAsNumber = ((Terms.Bucket) bucket).getKeyAsNumber();
            searchResponseAttrVo.setAttrId(keyAsNumber.longValue());

            ParsedStringTerms attrNameAgg = ((Terms.Bucket) bucket).getAggregations().get("attrNameAgg");
            //  获取桶中的数据：平台属性名称
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseAttrVo.setAttrName(attrName);

            //   获取桶中的数据：平台属性值名称
            ParsedStringTerms attrValueAgg = ((Terms.Bucket) bucket).getAggregations().get("attrValueAgg");

            //  第一种写法
            //            List<String> valueNameList = new ArrayList<>();
            //            attrValueAgg.getBuckets().forEach((buckets)->{
            //                String valueName = buckets.getKeyAsString();
            //                valueNameList.add(valueName);
            //            });
            //  第二种写法
            //            List<String> valueNameList = attrValueAgg.getBuckets().stream().map((buckets) -> {
            //                String valueName = buckets.getKeyAsString();
            //                return valueName;
            //            }).collect(Collectors.toList());

            //  第三种写法
            List<String> valueNameList = attrValueAgg.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
            searchResponseAttrVo.setAttrValueList(valueNameList);
            //  返回数据
            return searchResponseAttrVo;
        }).collect(Collectors.toList());

        //  赋值集合数据
        searchResponseVo.setAttrsList(attrsList);
        //  赋值完成！
        searchResponseVo.setTotal(hits.getTotalHits().value);
        //  返回对象
        return searchResponseVo;
    }

    //  生产dsl 语句返回 searchRequest
    private SearchRequest buildQueryDsl(SearchParam searchParam) {
        //  首先声明一个查询器 {}
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //  {bool}
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //  判断是否根据分类Id 进行检索
        if(searchParam.getCategory1Id()!=null){
            //  {query --bool -- filter --- term}
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id",searchParam.getCategory1Id()));
        }
        if(searchParam.getCategory2Id()!=null){
            //  {query --bool -- filter --- term}
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id",searchParam.getCategory2Id()));
        }
        if(searchParam.getCategory3Id()!=null){
            //  {query --bool -- filter --- term}
//            TermQueryBuilder category3Id = new TermQueryBuilder("category3Id", searchParam.getCategory3Id());
//            boolQueryBuilder.filter(category3Id);
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id",searchParam.getCategory3Id()));
        }
        //  还有可能根据关键字检索！
        if (!StringUtils.isEmpty(searchParam.getKeyword())){
            //  {query -- bool -- must --- match}
            //            MatchQueryBuilder matchQueryBuilder = new MatchQueryBuilder("title",searchParam.getKeyword()).operator(Operator.AND);
            //            boolQueryBuilder.must(matchQueryBuilder);
            boolQueryBuilder.must(QueryBuilders.matchQuery("title",searchParam.getKeyword()).operator(Operator.AND));
        }
        // 有可能根据品牌Id 进行过滤
        String trademark = searchParam.getTrademark();
        //  判断
        if (!StringUtils.isEmpty(trademark)){
            //  1:小米
            String[] split = trademark.split(":");
            if (split!=null && split.length==2){
                //  {bool -- filter -- term -- tmId}
                boolQueryBuilder.filter(QueryBuilders.termQuery("tmId",split[0]));
            }
        }
        //  有可能根据平台属性值过滤！
        String[] props = searchParam.getProps();
        //  平台属性Id:平台属性值名称:平台属性名
        if (props!=null && props.length>0){
            //  循环遍历
            for (String prop : props) {
                //  prop=平台属性Id:平台属性值名称:平台属性名
                //  对其进行分割
                String[] split = prop.split(":");
                if (split!=null && split.length==3){
                    //  声明两个bool
                    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                    //  表示内部的bool
                    BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();
                    //  赋值Id
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrId",split[0]));
                    //  赋值平台属性值名
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrValue",split[1]));
                    //  bool -- nested
                    boolQuery.must(QueryBuilders.nestedQuery("attrs",subBoolQuery, ScoreMode.None));
                    //  最外层的bool -- filter
                    boolQueryBuilder.filter(boolQuery);
                }
            }
        }
        //  {query --> bool }
        searchSourceBuilder.query(boolQueryBuilder);
        //  排序：
        //  获取排序规则
        String order = searchParam.getOrder();
        //  判断
        if (!StringUtils.isEmpty(order)){
            //  order=2:desc  2:asc
            //  分割字符串
            String[] split = order.split(":");
            if (split!=null && split.length ==2){
                String field = "";
                //  判断是按照哪种规则进行排序！
                switch (split[0]){
                    case "1":
                        field = "hotScore";
                        break;
                    case "2":
                        field = "price";
                        break;
                }
                searchSourceBuilder.sort(field,"asc".equals(split[1])? SortOrder.ASC:SortOrder.DESC);
                //  searchSourceBuilder.sort(new FieldSortBuilder("id").order(SortOrder.ASC));
            }else {
                searchSourceBuilder.sort("hotScore",SortOrder.DESC);
            }
        }
        //  高亮：
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.postTags("</span>");
        highlightBuilder.preTags("<span style=color:red>");
        searchSourceBuilder.highlighter(highlightBuilder);
        //  分页：
        int from = (searchParam.getPageNo()-1)*searchParam.getPageSize();
        searchSourceBuilder.from(from);
        searchSourceBuilder.size(searchParam.getPageSize()); // 默认3条
        //  聚合：
        //  品牌聚合：
        searchSourceBuilder.aggregation(AggregationBuilders.terms("tmIdAgg").field("tmId")
                .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
                .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl")));
        //  平台属性聚合:
        searchSourceBuilder.aggregation(AggregationBuilders.nested("attrAgg","attrs")
                    .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                    .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                    .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))));

        //  创建SearchRequest 对象
        //  GET /goods/_doc/17
        SearchRequest searchRequest = new SearchRequest("goods");
        searchRequest.source(searchSourceBuilder);
        //  searchRequest.types("_dock");
        //  设置哪些field 能够看到数据，哪些看不到数据！
        //  id,title....
        searchSourceBuilder.fetchSource(new String[]{"id","defaultImg","title","price"},null);
        //  代码。。。
        System.out.println("dsl:\t"+searchSourceBuilder.toString());
        //  返回数据
        return searchRequest;
    }
}
